﻿Imports System
Imports System.Drawing
Imports System.Windows.Forms
Imports System.Drawing.Drawing2D
Imports System.Collections.Generic

<System.ComponentModel.ToolboxItem(False)>
Public Class AutoCompleteListView
    Inherits UserControl
    Implements IAutoCompleteListView

    Private oldItemCount As Integer
    Private ToolTip As New ToolTip()

    Private _itemHeight As Integer
    Private _selectedItemIndex As Integer = -1
    Private _visibleItems As IList(Of AutoCompleteItem)

    Public Property HighlightedItemIndex() As Integer Implements IAutoCompleteListView.HighlightedItemIndex

    ''' <summary>
    ''' Duration (ms) of tooltip showing
    ''' </summary>
    Public Property ToolTipDuration() As Integer Implements IAutoCompleteListView.ToolTipDuration

    ''' <summary>
    ''' Display tooltip direction
    ''' </summary>
    Public Property ToolTipRightToLeft() As RightToLeft Implements IAutoCompleteListView.ToolTipRightToLeft

    ''' <summary>
    ''' Display tooltip font
    ''' </summary>
    ''' <returns></returns>
    Public Property ToolTipFont() As Font Implements IAutoCompleteListView.ToolTipFont

    ''' <summary>
    ''' Display tooltip title font
    ''' </summary>
    Public Property ToolTipTitleFont As Font Implements IAutoCompleteListView.ToolTipTitleFont

    ''' <summary>
    ''' Occurs when user selected item for inserting into text
    ''' </summary>
    Public Event ItemSelected As EventHandler Implements IAutoCompleteListView.ItemSelected

    ''' <summary>
    ''' Occurs when current hovered item is changing
    ''' </summary>
    Public Event ItemHovered As EventHandler(Of HoveredEventArgs) Implements IAutoCompleteListView.ItemHovered

    ''' <summary>
    ''' Colors
    ''' </summary>
    Public Property Colors() As Colors Implements IAutoCompleteListView.Colors

    Public Property ItemHeight() As Integer
        Get
            Return _itemHeight
        End Get
        Set(value As Integer)
            _itemHeight = value
            VerticalScroll.SmallChange = value
            oldItemCount = -1
            AdjustScroll()
        End Set
    End Property

    Public Overrides Property Font() As Font
        Get
            Return MyBase.Font
        End Get
        Set(value As Font)
            MyBase.Font = value
            ItemHeight = Font.Height + 2
        End Set
    End Property

    Public Property LeftPadding() As Integer

    Public Property ImageList() As ImageList Implements IAutoCompleteListView.ImageList

    Public Property VisibleItems() As IList(Of AutoCompleteItem) Implements IAutoCompleteListView.VisibleItems
        Get
            Return _visibleItems
        End Get
        Set(value As IList(Of AutoCompleteItem))
            _visibleItems = value
            SelectedItemIndex = -1
            AdjustScroll()
            Invalidate()
        End Set
    End Property

    Public Property SelectedItemIndex() As Integer Implements IAutoCompleteListView.SelectedItemIndex
        Get
            Return _selectedItemIndex
        End Get
        Set(value As Integer)
            Dim item As AutoCompleteItem = Nothing
            If value >= 0 AndAlso value < VisibleItems.Count Then
                item = VisibleItems(value)
            End If

            _selectedItemIndex = value

            OnItemHovered(New HoveredEventArgs() With {.Item = item})

            If item IsNot Nothing Then
                ShowToolTip(item)
                ScrollToSelected()
            End If

            Invalidate()
        End Set
    End Property

    Friend Sub New()
        SetStyle(ControlStyles.AllPaintingInWmPaint Or ControlStyles.OptimizedDoubleBuffer Or ControlStyles.UserPaint, True)
        MyBase.Font = New Font(FontFamily.GenericSansSerif, 9)
        ToolTipFont = MyBase.Font
        ToolTipTitleFont = New Font(MyBase.Font.FontFamily, MyBase.Font.Size, FontStyle.Bold, MyBase.Font.Unit)

        ItemHeight = Font.Height + 2
        VerticalScroll.SmallChange = ItemHeight
        BackColor = Color.White
        LeftPadding = 18
        ToolTipDuration = 3000
        Colors = New Colors()

        ToolTip.OwnerDraw = True
        AddHandler ToolTip.Draw, AddressOf ToolTip_Draw
        AddHandler ToolTip.Popup, AddressOf ToolTip_Popup
    End Sub

    Protected Overrides Sub Dispose(disposing As Boolean)
        If disposing Then ToolTip.Dispose()
        MyBase.Dispose(disposing)
    End Sub

    Private Sub OnItemHovered(e As HoveredEventArgs)
        RaiseEvent ItemHovered(Me, e)
    End Sub

    Private Sub AdjustScroll()
        If VisibleItems Is Nothing Then Return
        If oldItemCount = VisibleItems.Count Then Return

        Dim needHeight As Integer = ItemHeight * VisibleItems.Count + 1
        Height = Math.Min(needHeight, MaximumSize.Height)
        AutoScrollMinSize = New Size(0, needHeight)
        oldItemCount = VisibleItems.Count
    End Sub

    Private Sub ScrollToSelected()
        Dim y As Integer = SelectedItemIndex * ItemHeight - VerticalScroll.Value
        If y < 0 Then VerticalScroll.Value = SelectedItemIndex * ItemHeight
        If y > ClientSize.Height - ItemHeight Then
            VerticalScroll.Value = Math.Min(VerticalScroll.Maximum, SelectedItemIndex * ItemHeight - ClientSize.Height + ItemHeight)
        End If
        'some magic for update scrolls
        AutoScrollMinSize -= New Size(1, 0)
        AutoScrollMinSize += New Size(1, 0)
    End Sub

    Public Function GetItemRectangle(itemIndex As Integer) As Rectangle Implements IAutoCompleteListView.GetItemRectangle
        Dim y As Object = itemIndex * Me.ItemHeight - VerticalScroll.Value
        Return New Rectangle(0, y, ClientSize.Width - 1, Me.ItemHeight - 1)
    End Function

    Protected Overrides Sub OnPaintBackground(e As PaintEventArgs)
        e.Graphics.Clear(Colors.BackColor)
    End Sub

    Protected Overrides Sub OnPaint(e As PaintEventArgs)
        Dim rtl As Boolean = RightToLeft = RightToLeft.Yes
        AdjustScroll()
        Dim startI As Integer = VerticalScroll.Value / ItemHeight - 1
        Dim finishI As Integer = (VerticalScroll.Value + ClientSize.Height) / ItemHeight + 1
        startI = Math.Max(startI, 0)
        finishI = Math.Min(finishI, VisibleItems.Count)

        Dim y As Integer = 0
        For i = startI To finishI - 1
            y = i * ItemHeight - VerticalScroll.Value

            If ImageList IsNot Nothing AndAlso VisibleItems(i).ImageIndex >= 0 Then
                If rtl Then
                    e.Graphics.DrawImage(ImageList.Images(VisibleItems(i).ImageIndex), Width - 1 - LeftPadding, y)
                Else
                    e.Graphics.DrawImage(ImageList.Images(VisibleItems(i).ImageIndex), 1, y)
                End If
            End If

            Dim textRect As New Rectangle(LeftPadding, y, ClientSize.Width - 1 - LeftPadding, ItemHeight - 1)
            If rtl Then textRect = New Rectangle(1, y, ClientSize.Width - 1 - LeftPadding, ItemHeight - 1)

            If i = SelectedItemIndex Then
                Dim selectedBrush As Brush = New LinearGradientBrush(New Point(0, y - 3), New Point(0, y + ItemHeight), Colors.SelectedBackColor2, Colors.SelectedBackColor)
                e.Graphics.FillRectangle(selectedBrush, textRect)
                Using pen = New Pen(Colors.SelectedBackColor2)
                    e.Graphics.DrawRectangle(pen, textRect)
                End Using
            End If

            If i = HighlightedItemIndex Then
                Using pen = New Pen(Colors.HighlightingColor)
                    e.Graphics.DrawRectangle(pen, textRect)
                End Using
            End If

            Dim sf As New StringFormat()
            If rtl Then sf.FormatFlags = StringFormatFlags.DirectionRightToLeft

            Dim args As New PaintItemEventArgs(e.Graphics, e.ClipRectangle) With {
                .Font = Font,
                .TextRect = New RectangleF(textRect.Location, textRect.Size),
                .StringFormat = sf,
                .IsSelected = i = SelectedItemIndex,
                .IsHovered = i = HighlightedItemIndex,
                .Colors = Colors
            }
            'call drawing
            VisibleItems(i).OnPaint(args)
        Next
    End Sub

    Protected Overrides Sub OnScroll(se As ScrollEventArgs)
        MyBase.OnScroll(se)
        Invalidate(True)
    End Sub

    Protected Overrides Sub OnMouseClick(e As MouseEventArgs)
        MyBase.OnMouseClick(e)

        If e.Button = MouseButtons.Left Then
            SelectedItemIndex = PointToItemIndex(e.Location)
            ScrollToSelected()
            Invalidate()
        End If
    End Sub

    Private mouseEnterPoint As Point
    Protected Overrides Sub OnMouseEnter(e As EventArgs)
        MyBase.OnMouseEnter(e)
        mouseEnterPoint = Control.MousePosition
    End Sub
    Protected Overrides Sub OnMouseMove(e As MouseEventArgs)
        MyBase.OnMouseMove(e)

        If mouseEnterPoint <> Control.MousePosition Then
            HighlightedItemIndex = PointToItemIndex(e.Location)
            Invalidate()
        End If
    End Sub

    Protected Overrides Sub OnMouseDoubleClick(e As MouseEventArgs)
        MyBase.OnMouseDoubleClick(e)
        SelectedItemIndex = PointToItemIndex(e.Location)
        Invalidate()
        OnItemSelected()
    End Sub

    Private Sub OnItemSelected()
        RaiseEvent ItemSelected(Me, EventArgs.Empty)
    End Sub

    Private Function PointToItemIndex(p As Point) As Integer
        Return (p.Y + VerticalScroll.Value) / ItemHeight
    End Function

    Protected Overrides Function ProcessCmdKey(ByRef msg As Message, keyData As Keys) As Boolean
        Dim host = CType(Parent, AutoCompleteMenuHost)
        If host IsNot Nothing Then
            If host.Menu.ProcessKey(keyData, Keys.None) Then
                Return True
            End If
        End If

        Return MyBase.ProcessCmdKey(msg, keyData)
    End Function

    Public Sub SelectItem(itemIndex As Integer)
        SelectedItemIndex = itemIndex
        ScrollToSelected()
        Invalidate()
    End Sub

    Public Sub SetItems(items As List(Of AutoCompleteItem))
        VisibleItems = items
        SelectedItemIndex = -1
        AdjustScroll()
        Invalidate()
    End Sub

    Private _tooltiptext As String
    Private _control As Control
    Private Function getToolTipSize() As Size
        Dim size1 = TextRenderer.MeasureText(_tooltiptext, ToolTipFont)
        Dim size2 = If(Trim(ToolTip.ToolTipTitle) <> "", TextRenderer.MeasureText(ToolTip.ToolTipTitle, ToolTipTitleFont), New Size(0, 0))
        Dim width = Math.Max(size1.Width, size2.Width) + Math.Max(ToolTipFont.Height, ToolTipTitleFont.Height)
        Dim height = size1.Height + size2.Height + ToolTipTitleFont.Height * If(Trim(ToolTip.ToolTipTitle) <> "", 3 / 2, 1 / 2)
        Return New Size(width, height)
    End Function
    Private Sub ToolTip_Popup(sender As Object, e As PopupEventArgs)
        e.ToolTipSize = getToolTipSize()
    End Sub
    Private Sub ToolTip_Draw(sender As Object, e As DrawToolTipEventArgs)
        'e.Graphics.FillRectangle(New SolidBrush(Colors.BackColor), e.Bounds)
        e.DrawBackground()
        e.DrawBorder()
        Using sf As StringFormat = New StringFormat
            Dim rect As Rectangle = e.Bounds
            Dim value As Single = ToolTipTitleFont.Height / 2
            rect.Y += value
            If ToolTipRightToLeft = RightToLeft.Yes Then
                rect.X -= value
                sf.FormatFlags = StringFormatFlags.DirectionRightToLeft
            Else
                rect.X += value
            End If

            If (Trim(ToolTip.ToolTipTitle) <> "") Then
                e.Graphics.DrawString(ToolTip.ToolTipTitle, ToolTipTitleFont, New SolidBrush(Colors.ForeColor), rect, sf)
                rect.Y += TextRenderer.MeasureText(ToolTip.ToolTipTitle, ToolTipTitleFont).Height * 3 / 2
            End If

            e.Graphics.DrawString(e.ToolTipText, ToolTipFont, New SolidBrush(Colors.ForeColor), rect, sf)
        End Using
    End Sub
    Public Sub ShowToolTip(autocompleteItem As AutoCompleteItem, Optional control As Control = Nothing) Implements IAutoCompleteListView.ShowToolTip
        Dim title As String = autocompleteItem.ToolTipTitle
        Dim text As String = autocompleteItem.ToolTipText
        If control Is Nothing Then control = Me

        If String.IsNullOrEmpty(title) Then
            ToolTip.ToolTipTitle = Nothing
            ToolTip.SetToolTip(control, Nothing)
            Return
        End If

        Dim rtl As Boolean = RightToLeft = RightToLeft.Yes
        text = If(String.IsNullOrEmpty(text), title, text)
        title = If(String.IsNullOrEmpty(text), Nothing, title)

        _control = control
        _tooltiptext = text
        ToolTip.ToolTipTitle = title
        Dim x = If(rtl, getToolTipSize().Width, Width) + 3
        ToolTip.Show(text, control, x * If(rtl, -1, 1), 0, ToolTipDuration)
    End Sub
    Public Sub HideToolTip() Implements IAutoCompleteListView.HideToolTip
        ToolTip.Hide(If(_control, Me))
    End Sub
End Class